/* Copyright 2009
 *
 * This program and the accompanying materials
 * are made available under the terms of the
 * Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at
 *
 * 		http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Contributors:
 * 	   IBM Corporation - initial API and implementation for JDT/DLTK
 *     Sean W. Quinn - initial adoption for use with PHP from various sources.
 */
package org.eclipse.php.internal.core;

import java.util.ArrayList;
import java.util.Map;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IParent;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.WorkingCopyOwner;
import org.eclipse.dltk.internal.core.BuildpathEntry;
import org.eclipse.dltk.internal.core.OpenableElementInfo;
import org.eclipse.dltk.internal.core.ScriptProject;
import org.eclipse.dltk.internal.core.util.MementoTokenizer;
import org.eclipse.dltk.utils.CorePrinter;
import org.eclipse.php.core.INamespaceFragment;
import org.eclipse.php.core.INamespaceFragmentRoot;
import org.eclipse.php.core.IPHPElement;
import org.eclipse.php.core.IPHPModel;
import org.eclipse.php.core.IPHPModelStatusConstants;
import org.eclipse.php.core.IPHPProject;
import org.eclipse.php.core.PHPModelException;

/**
 * @see INamespaceFragmentRoot
 */
@SuppressWarnings("restriction")
public class NamespaceFragmentRoot extends org.eclipse.dltk.internal.core.Openable implements INamespaceFragmentRoot {

	/**
	 * The delimiter between the source path and root path in the attachment
	 * server property.
	 */
	protected final static char ATTACHMENT_PROPERTY_DELIMITER = '*';
	/*
	 * No source attachment property
	 */
	public final static String NO_SOURCE_ATTACHMENT = ""; //$NON-NLS-1$

	/**
	 * The resource associated with this root (null for external jar)
	 */
	protected IResource resource;

	/**
	 * Constructs a package fragment root which is the root of the PHP package
	 * directory hierarchy.
	 */
	protected NamespaceFragmentRoot(IResource resource, ScriptProject project) {
		super(project);
		this.resource = resource;
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public void attachSource(IPath sourcePath, IPath rootPath, IProgressMonitor monitor) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#attachSource() not yet implemented!");
	}

	/**
	 * @see Openable
	 */
	@Override
	protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements,
			IResource underlyingResource) throws ModelException {
		((NamespaceFragmentRootInfo) info).setRootKind(determineKind(underlyingResource));
		return computeChildren(info, underlyingResource);
	}

	SourceMapper createSourceMapper(IPath sourcePath, IPath rootPath) {
		SourceMapper mapper = new SourceMapper(sourcePath, rootPath == null ? null : rootPath.toOSString(),
				getScriptProject().getOptions(true)); // cannot use workspace options if external jar is 1.5 jar and workspace options are 1.4 options
		return mapper;
	}

	/*
	 * @see org.eclipse.jdt.core.INamespaceFragmentRoot#delete
	 */
	public void delete(int updateResourceFlags, int updateModelFlags, IProgressMonitor monitor)
			throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#delete() not yet implemented!");
	}

	/**
	 * Compute the package fragment children of this package fragment root.
	 *
	 * @exception PHPModelException The resource associated with this package
	 *                fragment root does not exist
	 */
	protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#computeChildren() not yet implemented!");
	}

	/**
	 * Starting at this folder, create package fragments and add the fragments
	 * that are not exclused to the collection of children.
	 *
	 * @exception PHPModelException The resource associated with this package
	 *                fragment does not exist
	 */
	protected void computeFolderChildren(IContainer folder, boolean isIncluded, String[] pkgName, ArrayList vChildren,
			char[][] inclusionPatterns, char[][] exclusionPatterns) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#computeFolderChildren() not yet implemented!");
	}

	/*
	 * @see org.eclipse.jdt.core.INamespaceFragmentRoot#copy
	 */
	public void copy(IPath destination, int updateResourceFlags, int updateModelFlags, IBuildpathEntry sibling,
			IProgressMonitor monitor) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#copy() not yet implemented!");
	}

	/**
	 * Returns a new element info for this element.
	 */
	@Override
	protected Object createElementInfo() {
		return new NamespaceFragmentRootInfo();
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public INamespaceFragment createNamespaceFragment(String pkgName, boolean force, IProgressMonitor monitor)
			throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#createNamespaceFragment() not yet implemented!");
	}

	/**
	 * Returns the root's kind - K_SOURCE or K_BINARY, defaults to K_SOURCE if
	 * it is not on the classpath.
	 *
	 * @exception PHPModelException if the project and root do not exist.
	 */
	protected int determineKind(IResource underlyingResource) throws ModelException {
		IBuildpathEntry entry = ((ScriptProject) getScriptProject()).getBuildpathEntryFor(underlyingResource
				.getFullPath());
		if (entry != null) {
			return entry.getContentKind();
		}
		return INamespaceFragmentRoot.K_SOURCE;
	}

	/**
	 * Compares two objects for equality; for <code>NamespaceFragmentRoot</code>
	 * s, equality is having the same parent, same resources, and occurrence
	 * count.
	 */
	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (!(o instanceof NamespaceFragmentRoot))
			return false;
		NamespaceFragmentRoot other = (NamespaceFragmentRoot) o;
		return getResource().equals(other.getResource()) && this.parent.equals(other.parent);
	}

	/**
	 *
	 * @return
	 */
	private IBuildpathEntry findSourceAttachmentRecommendation() {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getHandleFromMemento() not yet implemented!");
	}

	/*
	 * Returns the exclusion patterns from the classpath entry associated with
	 * this root.
	 */
	public char[][] fullExclusionPatternChars() throws ModelException {
		try {
			if (isOpen() && getKind() != INamespaceFragmentRoot.K_SOURCE)
				return null;
			BuildpathEntry entry = (BuildpathEntry) getRawBuildpathEntry();
			if (entry == null) {
				return null;
			}
			else {
				return entry.fullExclusionPatternChars();
			}
		}
		catch (PHPModelException e) {
			return null;
		}
	}

	/*
	 * Returns the inclusion patterns from the classpath entry associated with
	 * this root.
	 */
	public char[][] fullInclusionPatternChars() throws ModelException {
		try {
			if (isOpen() && getKind() != INamespaceFragmentRoot.K_SOURCE)
				return null;
			BuildpathEntry entry = (BuildpathEntry) getRawBuildpathEntry();
			if (entry == null) {
				return null;
			}
			else {
				return entry.fullInclusionPatternChars();
			}
		}
		catch (PHPModelException e) {
			return null;
		}
	}

	@Override
	public String getElementName() {
		IResource res = getResource();
		if (res instanceof IFolder)
			return ((IFolder) res).getName();
		return ""; //$NON-NLS-1$
	}

	/**
	 * @see IPHPElement
	 */
	public int getElementType() {
		return NAMESPACE_FRAGMENT_ROOT;
	}

	/**
	 * @see PHPElement#getHandleMemento()
	 */
	@Override
	protected char getHandleMementoDelimiter() {
		return PHPElement.JEM_NAMESPACEFRAGMENTROOT;
	}

	/*
	 * @see PHPElement
	 */
	@Override
	public IPHPElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getHandleFromMemento() not yet implemented!");
	}

	/**
	 * @see PHPElement#getHandleMemento(StringBuffer)
	 */
	@Override
	protected void getHandleMemento(StringBuffer buff) {
		IPath path;
		IResource underlyingResource = getResource();
		if (underlyingResource != null) {
			// internal jar or regular root
			if (getResource().getProject().equals(getScriptProject().getProject())) {
				path = underlyingResource.getProjectRelativePath();
			}
			else {
				path = underlyingResource.getFullPath();
			}
		}
		else {
			// external jar
			path = getPath();
		}
		((PHPElement) getParent()).getHandleMemento(buff);
		buff.append(getHandleMementoDelimiter());
		escapeMementoName(buff, path.toString());
	}

	/**
	 * @throws ModelException
	 * @see INamespaceFragmentRoot
	 */
	public int getKind() throws ModelException {
		return ((NamespaceFragmentRootInfo) getElementInfo()).getRootKind();
	}

	/*
	 * A version of getKind() that doesn't update the timestamp of the info in
	 * the PHP model cache to speed things up
	 */
	int internalKind() throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#internalKind() not yet implemented!");
	}

	/**
	 * Returns an array of non-PHP resources contained in the receiver.
	 * @throws ModelException
	 */
	public Object[] getNonPHPResources() throws ModelException {
		return ((NamespaceFragmentRootInfo) getElementInfo()).getNonPHPResources(getScriptProject(), getResource(), this);
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public INamespaceFragment getNamespaceFragment(String packageName) {
		// tolerate package names with spaces (e.g. 'x . y') (http://bugs.eclipse.org/bugs/show_bug.cgi?id=21957)
		//String[] pkgName = Util.getTrimmedSimpleNames(packageName);
		//return getNamespaceFragment(pkgName);
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getNamespaceFragment() not yet implemented!");
	}

	public NamespaceFragment getNamespaceFragment(String[] pkgName) {
		return new NamespaceFragment(this, pkgName);
	}

	/**
	 * Returns the package name for the given folder (which is a decendent of
	 * this root).
	 */
	protected String getPackageName(IFolder folder) {
		IPath myPath = getPath();
		IPath pkgPath = folder.getFullPath();
		int mySegmentCount = myPath.segmentCount();
		int pkgSegmentCount = pkgPath.segmentCount();
		StringBuffer pkgName = new StringBuffer(INamespaceFragment.DEFAULT_PACKAGE_NAME);
		for (int i = mySegmentCount; i < pkgSegmentCount; i++) {
			if (i > mySegmentCount) {
				pkgName.append('.');
			}
			pkgName.append(pkgPath.segment(i));
		}
		return pkgName.toString();
	}

	/**
	 * @see IPHPElement
	 */
	public IPath getPath() {
		return internalPath();
	}

	public IPath internalPath() {
		return getResource().getFullPath();
	}

	/*
	 * @see INamespaceFragmentRoot
	 */
	public IBuildpathEntry getRawBuildpathEntry() throws ModelException {

		IBuildpathEntry rawEntry = null;
		ScriptProject project = (ScriptProject) getScriptProject();
		project.getResolvedBuildpath(); // force the reverse rawEntry cache to be populated
		Map rootPathToRawEntries = project.getPerProjectInfo().resolvedPathToRawEntries;
		if (rootPathToRawEntries != null) {
			rawEntry = (IBuildpathEntry) rootPathToRawEntries.get(getPath());
		}
		if (rawEntry == null) {
			throw new PHPModelException(new PHPModelStatus(IPHPModelStatusConstants.ELEMENT_NOT_ON_BUILDPATH, this));
		}
		return rawEntry;
	}

	/**
	 * @return
	 */
	public IResource getResource() {
		if (this.resource != null) {
			return this.resource;
		}
		throw new NullPointerException("Resource was null.");
	}

	/*
	 * @see IPHPElement
	 */
	public IResource resource(NamespaceFragmentRoot root) {
		return this.resource;
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public IPath getSourceAttachmentPath() throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getSourceAttachmentPath() not yet implemented!");
	}

	/**
	 * For use by <code>AttachSourceOperation</code> only. Sets the source
	 * mapper associated with this root.
	 */
	public void setSourceMapper(SourceMapper mapper) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#setSourceMapper() not yet implemented!");
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public IPath getSourceAttachmentRootPath() throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getSourceAttachmentRootPath() not yet implemented!");
	}

	/**
	 * @see PHPElement
	 */
	public SourceMapper getSourceMapper() {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getSourceMapper() not yet implemented!");
	}

	/**
	 * @see IPHPElement
	 */
	@Override
	public IResource getUnderlyingResource() throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getUnderlyingResource() not yet implemented!");
	}

	/**
	 * @see IParent
	 */
	@Override
	public boolean hasChildren() throws PHPModelException {
		// a namespace fragment root always has the default namespace as a child
		return true;
	}

	@Override
	public int hashCode() {
		return getResource().hashCode();
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public boolean isArchive() {
		return false;
	}

	/**
	 * @see INamespaceFragmentRoot
	 */
	public boolean isExternal() {
		return false;
	}

	/*
	 * Validate whether this package fragment root is on the classpath of its
	 * project.
	 */
	protected IStatus validateOnBuildpath() {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#validateOnBuildpath() not yet implemented!");
	}

	/*
	 * @see org.eclipse.jdt.core.INamespaceFragmentRoot#move
	 */
	public void move(IPath destination, int updateResourceFlags, int updateModelFlags, IBuildpathEntry sibling,
			IProgressMonitor monitor) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#move() not yet implemented!");
	}

	/**
	 * @private Debugging purposes
	 */
	@Override
	protected void toStringInfo(int tab, StringBuffer buffer, Object info, boolean showResolvedInfo) {
		buffer.append(tabString(tab));
		IPath path = getPath();
		if (isExternal()) {
			buffer.append(path.toOSString());
		}
		else if (getScriptProject().getElementName().equals(path.segment(0))) {
			if (path.segmentCount() == 1) {
				buffer.append("<project root>"); //$NON-NLS-1$
			}
			else {
				buffer.append(path.removeFirstSegments(1).makeRelative());
			}
		}
		else {
			buffer.append(path);
		}
		if (info == null) {
			buffer.append(" (not open)"); //$NON-NLS-1$
		}
	}

	protected IStatus validateExistence(IResource underlyingResource) {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#validateExistence() not yet implemented!");
	}

	/**
	 * Possible failures:
	 * <ul>
	 * <li>ELEMENT_NOT_PRESENT - the root supplied to the operation does not
	 * exist
	 * <li>INVALID_ELEMENT_TYPES - the root is not of kind K_BINARY
	 * <li>RELATIVE_PATH - the path supplied to this operation must be an
	 * absolute path
	 * </ul>
	 */
	protected void verifyAttachSource(IPath sourcePath) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#verifyAttachSource() not yet implemented!");
	}

	public IPHPModel getPHPModel() {
		IPHPModel model = (IPHPModel) this.getModel();
		return model;
	}

	public IPHPProject getPHPProject() {
		IPHPProject project = (IPHPProject) this.getScriptProject();
		return project;
	}

	public ISchedulingRule getSchedulingRule() {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#ISchedulingRule() not yet implemented!");
	}

	@Override
	public void printNode(CorePrinter arg0) {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#printNode() not yet implemented!");
	}

	public String getAttachedPHPdoc(IProgressMonitor monitor) throws PHPModelException {
		// FIXME: Implement me!
		throw new RuntimeException(getClass().getName() + "#getAttachedPHPdoc() not yet implemented!");
	}
}
